package de.szut.logic;

import android.app.AlertDialog;
import android.content.Context;
import android.util.Log;
import de.szut.entity.Country;
import de.szut.entity.OperatingSystem;
import de.szut.entity.Points;
import de.szut.entity.User;
import de.szut.interfaces.GameEventListener;
import de.szut.interfaces.ILogic;
import de.szut.interfaces.IResearch;
import de.szut.main.ResourcesManager;
import de.szut.util.ModifierType;
import de.szut.util.OperatingSystemName;
import de.szut.util.PointIncreaseType;
import de.szut.util.Settings;
import org.andengine.engine.handler.timer.ITimerCallback;
import org.andengine.engine.handler.timer.TimerHandler;

import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

/**
 * TODO: LOGS
 * Created by Dmitrij on 27.09.13.
 */
public class Logic implements ILogic, Serializable {

    private ModifierStore modifierStore;

    private List<Country> infectedCountries = new ArrayList<Country>();

    private List<Country> nonInfectedCountries = new ArrayList<Country>();

    private List<Country> controlledCountries = new ArrayList<Country>();

    private List<IResearch> researches = new ArrayList<IResearch>();

    private List<OperatingSystem> operatingSystemList = new ArrayList<OperatingSystem>();

    private HashMap<ModifierType, Double> modifierValues = new HashMap<ModifierType, Double>();

    private static Logic instance = null;

    public static void setInstance(Logic instance) {
        Logic.instance = instance;
    }

    private Country firstInfectedCountry = null;

    private Country currentTryToInfect = null;

    private GameEventListener gameEventListener;

    private boolean researchHasStarted = false;

    private static final String TAG = "Splasher_Logic";

    public static Logic getInstance(Context context) {
        if (instance == null) {
            instance = new Logic(context);
        }
        return instance;
    }

    private Logic(Context context) {
        this.modifierStore = ModifierStore.getInstance(context);
        this.infectedCountries = this.modifierStore.getHelper().getCountryDao().getInfectedCountries();
        this.nonInfectedCountries = this.modifierStore.getHelper().getCountryDao().getNotInfectedCountries();
        this.createResearches();
    }

    @Override
    public void onCalculate() {
        if (this.nonInfectedCountries.isEmpty()) {
            this.nonInfectedCountries = this.modifierStore.getHelper().getCountryDao().getNotInfectedCountries();
        }
        if (!this.gameHasStarted()) {
            this.gameEventListener.onGameStart();
            Log.i(TAG, "game has started... user should select the first infected country");
        } else if (this.gameIsWon()) {
            this.gameEventListener.onGameWon();
            Log.i(TAG, "game is finished, the whole world is infected...");
        } else if (this.gameIsOver()) {
            this.gameEventListener.onGameLost();
            Log.i(TAG, "anti virus research has finished");
        } else {
            this.reCalculateModifiers();
            this.calculateInfectedCountries();
            this.infectNewCountries();
            this.calculateControlledCountries();
            this.researchAntiVirus();
            this.gameEventListener.onGameProgress();
        }
    }

    private void calculateControlledCountries() {
        Log.i(TAG, "starting to increase the amount of controlled users");
        for (Country country : this.infectedCountries) {
            for (OperatingSystem os : country.getOperatingSystems()) {
                if (os.getUsers().getPercentageOfInfection() > Settings.MIN_INFECTION_RATE && this.modifierValues.get(ModifierType.DEATH_RATE) > Settings.MIN_DEATH_RATE) {
                    Log.i(TAG, "amount of controlled users increased for : " + country.getName());
                    os.getUsers().addControlledDecreaseInfectedUsers(this.calcUsersToControll(os));
                }
                if (os.getUsers().getControlledUsers() == os.getUsers().getTotalUsers()) {
                    infectedCountries.remove(country);
                    controlledCountries.add(country);
                }
            }

        }
    }

    public boolean gameHasStarted() {
        boolean gameHasStarted = false;
        if (this.firstInfectedCountry != null) {
            gameHasStarted = true;
        }
        return gameHasStarted;
    }

    @Override
    public void infectCountries(ArrayList<Country> newInfectedCountries) {
        for (Country country : newInfectedCountries) {
            this.infectedCountries.add(country);
            for (Iterator<Country> it = nonInfectedCountries.iterator(); it.hasNext(); ) {
                Country nextCountry = it.next();
                if (nextCountry.getName().equals(country.getName())) {
                    it.remove();
                }
            }
            for (OperatingSystem os : country.getOperatingSystems()) {
                os.getUsers().setInfectedUsers(1);
            }
            if (this.firstInfectedCountry == null) {
                this.firstInfectedCountry = country;
            } else {
                this.increasePointValueByType(PointIncreaseType.INFECTED_NEW_COUNTRY);
            }
        }
    }

    @Override
    public void infectCountry(Country infectedCountry) {
        for (OperatingSystem os : infectedCountry.getOperatingSystems()) {
            os.getUsers().setInfectedUsers(1);
        }
        this.gameEventListener.updateGameHudViewUser(infectedCountry);
        this.gameEventListener.newCountryIsInfected(infectedCountry);
        this.infectedCountries.add(infectedCountry);
        this.nonInfectedCountries.remove(infectedCountry);
        if (this.firstInfectedCountry == null) {
            this.firstInfectedCountry = infectedCountry;
        } else {
            this.increasePointValueByType(PointIncreaseType.INFECTED_NEW_COUNTRY);
        }
    }

    @Override
    public void setGameEventListener(GameEventListener listener) {
        this.gameEventListener = listener;
    }

    @Override
    public Points getPointsForCurrentUser() {
        return this.modifierStore.getPointsForCurrentUser();
    }

    @Override
    public ModifierStore getModifierStore() {
        return this.modifierStore;
    }

    private void increasePointValueByType(PointIncreaseType type) {
        this.getPointsForCurrentUser().increaseValueByType(type);
        this.modifierStore.getHelper().getPointsDao().savePoints(this.getPointsForCurrentUser());
    }

    private void createResearches() {
        IResearch macResearch = new Research(OperatingSystemName.MAC);
        IResearch linuxResearch = new Research(OperatingSystemName.LINUX);
        IResearch windowsResearch = new Research(OperatingSystemName.WINDOWS);
        this.researches.add(macResearch);
        this.researches.add(linuxResearch);
        this.researches.add(windowsResearch);
    }

    private void infectNewCountries() {
        Log.i(TAG, "trying to infect new countries");
//        ArrayList<Country> newInfectedCountries = new ArrayList<Country>();
        if (!this.nonInfectedCountries.isEmpty()) {
//            for (Country country : this.nonInfectedCountries) {
            Country country = getRandomCountry();
            boolean success = this.tryInfection(country);
            if (success) {
                gameEventListener.newCountryIsInfected(country);
                this.infectCountry(country);
                Log.i(TAG, "successfully infected :" + country.getName());
            } else {
                country.increasePassedIntervals();
            }
//            }
//            this.infectCountries(newInfectedCountries);
        } else {
            Log.i(TAG, "NON_INFECTED IS EMPTY");
        }
        Log.i(TAG, "infection of new countries has ended");
    }

    /**
     * TODO: does this make any sense?
     * TODO: infectionChance = NaN;
     *
     * @param countryToInfect
     * @return
     */
    private boolean tryInfection(Country countryToInfect) {
        boolean infected = false;
        double infectedUsers = this.getTotalInfectionInPercent();
        double spreadRate = this.getModifierValueByType(ModifierType.SPREAD_RATE);
        double infectionChance = Math.pow(infectedUsers, spreadRate) / countryToInfect.getSecurityLevel().getSecurityLevel();
        Log.i(TAG, "chance to infect: " + infectionChance);
        // increase chance for noobs
//        if (countryToInfect.getPassedIntervals() >= Settings.BONUS_INTERVAL_COUNT) {
//            infectionChance = infectionChance * countryToInfect.getPassedIntervals();
//        }
        double random = Math.random();
        if (random * 100.0 <= infectionChance || infectedUsers > Settings.MAX_INFECTION_BORDER) {
            infected = true;
        }
        return infected;
    }

    /**
     * TODO wrong shit
     *
     * @return
     */

    private double getTotalInfectionInPercent() {
        double totalInfectionInPercent = 0.0;
        for (Country country : infectedCountries) {
            for (OperatingSystem opSys : country.getOperatingSystems()) {
                totalInfectionInPercent += opSys.getUsers().getPercentageOfInfection();
            }
        }
//        for (OperatingSystem os : this.getTotalSpecifiedOperatingSystems()) {
//            totalInfectionInPercent += os.getUsers().getPercentageOfInfection();
//            if (totalInfectionInPercent > os.getUsers().getPercentageOfInfection()) {
//                totalInfectionInPercent = totalInfectionInPercent / 2;
//            }
//        }
        return totalInfectionInPercent;
    }

    private Country getRandomCountry() {
        return this.nonInfectedCountries.get((int) (Math.random() * this.nonInfectedCountries.size()));
    }

    private void researchAntiVirus() {
        double totalResearchProgress = 0;
        Log.i(TAG, "starting to research anti virus");
        for (Country country : getAllCountries()) {
            for (OperatingSystem os : country.getOperatingSystems()) {
                if (os.getUsers().getPercentageOfInfection() > Settings.MIN_INFECTION_FOR_RESEARCH) {
                    IResearch research = this.getResearchByOperatingSystem(os.getOperatingSystemName());
                    if (!research.isStarted()) {
                        gameEventListener.showResearchHasStartedDialog(os.getOperatingSystemName().name());
                        Log.i(TAG, "new research has started for : " + os.getOperatingSystemName().name());
                        research.start();
                    } else {
                        research.process(this.modifierValues.get(ModifierType.RESISTANCE_RATE));
                        Log.i(TAG, "research increased for : " + os.getOperatingSystemName().name());
                        totalResearchProgress += research.getResearchProcess();
                    }

                }
            }
        }
//        if(researchHasStarted){
//            gameEventListener.showResearchHasStartedDialog();
//        }
        gameEventListener.updateGameHudViewResearch(new DecimalFormat("0.00").format(totalResearchProgress / 3));
        Log.i(TAG, "research has ended");
    }

    public ArrayList<Country> getAllCountries() {
        ArrayList<Country> allCountries = new ArrayList<Country>();
        allCountries.addAll(getNonInfectedCountries());
        allCountries.addAll(getInfectedCountries());
        allCountries.addAll(getControlledCountries());
        return allCountries;
    }

    private boolean gameIsWon() {
        Boolean isGameWon = null;
        for (User user : this.getAllUser(getAllUsersInCountriesMap())) {
            if (user.getPercentageOfControlling() == 100.0 && (isGameWon || isGameWon == null)) {
                isGameWon = Boolean.TRUE;
            } else {
                isGameWon = Boolean.FALSE;
            }
        }
        Log.i(TAG, "infectedCountries :" + this.infectedCountries.size());
        Log.i(TAG, "notInfectedCountries :" + this.nonInfectedCountries.size());
        return isGameWon;
    }

    private boolean gameIsOver() {
        Boolean isGameOver = null;
        for (IResearch research : this.researches) {
            if (research.isStarted() && research.getResearchProcess() == Research.PROGRESS_DONE && (isGameOver == null || isGameOver)) {
                isGameOver = Boolean.TRUE;
            } else {
                isGameOver = Boolean.FALSE;
            }
        }
        return isGameOver;
    }

    private IResearch getResearchByOperatingSystem(OperatingSystemName operatingSystemName) {
        IResearch research = null;
        for (IResearch res : this.researches) {
            if (res.getOperatingSystem().equals(operatingSystemName)) {
                research = res;
            }
        }
        return research;
    }

    private List<OperatingSystem> getTotalSpecifiedOperatingSystems() {
        if (operatingSystemList.isEmpty()) {
            OperatingSystem mac = new OperatingSystem(OperatingSystemName.MAC, new User(0));
            OperatingSystem linux = new OperatingSystem(OperatingSystemName.LINUX, new User(0));
            OperatingSystem windows = new OperatingSystem(OperatingSystemName.WINDOWS, new User(0));
            operatingSystemList.add(mac);
            operatingSystemList.add(linux);
            operatingSystemList.add(windows);
        }
        this.countUsers(operatingSystemList, this.infectedCountries, this.nonInfectedCountries);
        return operatingSystemList;
    }


    public void countUsers(List<OperatingSystem> operatingSystemList, List<Country>... countryLists) {
        for (List<Country> countryList : countryLists) {
            for (Country country : countryList) {
                for (OperatingSystem os : country.getOperatingSystems()) {
                    this.addUsersToOperatingSystem(this.getOperatingSystemByName(operatingSystemList, os.getOperatingSystemName()), os.getUsers());
//                    this.addUsersToOperatingSystem(os, os.getUsers());
                }
            }
        }
    }

    /**
     * TODO: Change setInfected/nonInfected users
     *
     * @param operatingSystem
     * @param users
     */
    private void addUsersToOperatingSystem(OperatingSystem operatingSystem, User users) {
        User osUser = operatingSystem.getUsers();
        if (osUser.getTotalUsers() > 0) {
            osUser.clearAllUsers();
        }
        osUser.addTotalUsers(users.getTotalUsers());
        osUser.addNotInfectedUsers(users.getNotInfectedUsers());
        osUser.addInfectedDecreaseNotInfectedUser(users.getInfectedUsers());
        osUser.addControlledDecreaseInfectedUsers(users.getControlledUsers());
    }

    private OperatingSystem getOperatingSystemByName(List<OperatingSystem> osList, OperatingSystemName osName) {
        OperatingSystem operatingSystem = null;
        for (OperatingSystem os : osList) {
            if (os.getOperatingSystemName().equals(osName)) {
                operatingSystem = os;
            }
        }
        return operatingSystem;
    }

    private void calculateInfectedCountries() {
        Log.i(TAG, "starting to calculate new infection rates for every country...");
        for (Country infectedCountry : this.infectedCountries) {
            for (final OperatingSystem os : infectedCountry.getOperatingSystems()) {
                boolean infectionIncreased;
                if (os.getUsers().getPercentageOfInfection() < Settings.MIN_INFECTION_RATE) {
                    OperatingSystem manipulatedOs = os;
                    manipulatedOs.getUsers().setInfectedUsers(Settings.MIN_INFECTION_RATE);
                    infectionIncreased = this.increaseInfection(manipulatedOs, infectedCountry.getSecurityLevel().getSecurityLevel());
                } else {
                    infectionIncreased = this.increaseInfection(os, infectedCountry.getSecurityLevel().getSecurityLevel());
                }
                if (infectionIncreased) {
                    os.resetPassedIntervals();
                    //TODO when increase point
//                    if(){
                    this.increasePointValueByType(PointIncreaseType.INFECTED_MORE_USER);
//                    }
                    ResourcesManager.getInstance().getEngine().registerUpdateHandler(new TimerHandler(0.1f, new ITimerCallback() {
                        @Override
                        public void onTimePassed(TimerHandler pTimerHandler) {
                            ResourcesManager.getInstance().getEngine().unregisterUpdateHandler(pTimerHandler);
                            os.getUsers().addInfectedDecreaseNotInfectedUser(Logic.this.calcUsersToInfect(os));
                        }
                    }));
                    this.gameEventListener.populateInfectedUser(infectedCountry);
                    this.gameEventListener.updateGameHudViewUser(infectedCountry);
                    Log.i(TAG, "infection has increased for " + infectedCountry.getName());
                } else {
                    os.increasePassedIntervals();
                }
            }
        }
        Log.i(TAG, "end of calculation for every country");
    }

    /**
     * TODO: any sense detected?
     *
     * @param os
     * @return
     */
    private int calcUsersToInfect(OperatingSystem os) {
        int defaultUsers = 1;
        int users = os.getUsers().getInfectedUsers() * Settings.INFECTION_SPREAD_RATE / 100;
        if (users < defaultUsers) {
            return defaultUsers;
        }
        return users;
    }

    private int calcUsersToControll(OperatingSystem os) {
        int defaultUsers = 1;
        int users = os.getUsers().getControlledUsers() * Settings.INFECTION_SPREAD_RATE / 100;
        if (users < defaultUsers) {
            return defaultUsers;
        }
        return users;
    }

    private boolean increaseInfection(OperatingSystem os, double securityLevel) {
        boolean infected = false;
        int infectedUsers = os.getUsers().getInfectedUsers();
        double spreadRate = this.getModifierValueByType(ModifierType.SPREAD_RATE);
        double infectionChance = Math.pow(infectedUsers, spreadRate) / (securityLevel * os.getUsers().getNotInfectedUsers());
        // increase chance for noobs
        if (os.getPassedIntervals() >= Settings.BONUS_INTERVAL_COUNT) {
            infectionChance = infectionChance + (2 * os.getPassedIntervals());
        }
        double random = Math.random();
        if (random * 100.0 <= infectionChance || os.getUsers().getPercentageOfInfection() > Settings.MAX_INFECTION_BORDER) {
            infected = true;
        }
        return infected;
    }

    private void reCalculateModifiers() {
        Log.i(TAG, "reloading modifiers...");
        for (ModifierType modifierType : ModifierType.values()) {
            this.modifierValues.put(modifierType, this.modifierStore.getModifierValueByType(modifierType));
        }
    }

    /**
     * TODO Test else
     *
     * @param type
     * @return
     */
    private double getModifierValueByType(ModifierType type) {
        double modifierValue = this.modifierValues.get(type);
        if (modifierValue < Settings.MIN_SPREAD_RATE) {
            modifierValue = Settings.MIN_SPREAD_RATE;
        } else {
            modifierValue = Settings.MIN_SPREAD_RATE + ((0.2 / this.modifierStore.countModifierValueByType(type)) * modifierValue);
        }
        return modifierValue;
    }

    public ArrayList<User> getAllUser(HashMap<String, ArrayList<User>> countriesUserMap) {
        ArrayList<User> allUsers = new ArrayList<User>();
        for (String s : countriesUserMap.keySet()) {
            allUsers.addAll(countriesUserMap.get(s));
        }
        return allUsers;
    }

    public HashMap<String, ArrayList<User>> getAllUsersInCountriesMap() {
        HashMap<String, ArrayList<User>> countriesUserMap = new HashMap<String, ArrayList<User>>();
        ArrayList<Country> countries = new ArrayList<Country>();
        if (!getNonInfectedCountries().isEmpty())
            countries.addAll(getNonInfectedCountries());
        if (!getInfectedCountries().isEmpty())
            countries.addAll(getInfectedCountries());
        if (!getControlledCountries().isEmpty())
            countries.addAll(getControlledCountries());

        for (Country c : countries) {
            ArrayList<User> users = new ArrayList<User>();
            users.add(c.getOperatingSystems().getLinux().getUsers());
            users.add(c.getOperatingSystems().getMac().getUsers());
            users.add(c.getOperatingSystems().getWindows().getUsers());
            countriesUserMap.put(c.getName(), users);
        }
        return countriesUserMap;
    }

    public List<Country> getInfectedCountries() {
        return infectedCountries;
    }

    public void setInfectedCountries(List<Country> infectedCountries) {
        this.infectedCountries = infectedCountries;
    }

    public List<Country> getNonInfectedCountries() {
        return nonInfectedCountries;
    }

    public void setNonInfectedCountries(List<Country> nonInfectedCountries) {
        this.nonInfectedCountries = nonInfectedCountries;
    }

    public List<Country> getControlledCountries() {
        return controlledCountries;
    }

    public void setControlledCountries(List<Country> controlledCountries) {
        this.controlledCountries = controlledCountries;
    }

    public List<IResearch> getResearches() {
        return researches;
    }

    public void setResearches(List<IResearch> researches) {
        this.researches = researches;
    }

    public HashMap<ModifierType, Double> getModifierValues() {
        return modifierValues;
    }

    public void setModifierValues(HashMap<ModifierType, Double> modifierValues) {
        this.modifierValues = modifierValues;
    }

    public List<OperatingSystem> getOperatingSystemList() {
        return operatingSystemList;
    }

    public void setOperatingSystemList(List<OperatingSystem> operatingSystemList) {
        this.operatingSystemList = operatingSystemList;
    }

}
